﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

using FarseerGames.FarseerPhysics.Dynamics;

using Common;
using Microsoft.Xna.Framework.Graphics;
using FarseerGames.FarseerPhysics;
using Microsoft.Xna.Framework.Content;
using FarseerGames.FarseerPhysics.Factories;
using FarseerGames.FarseerPhysics.Collisions;

using System.Timers;
using Microsoft.Xna.Framework.Input;

namespace Matryoshka
{
    public class DollState
    {
        public Body Body = null;
        public Geom GeomHead = null;
        public Geom GeomButt = null;

        public Contraption Contraption = new Contraption();

        public float JumpForce = 0f;
        public float WalkForce = 0f;
        public float HopForce = 0f;
        public float GlideForce = 0f;
        public float StompForce = 0f;

        public DollState(float jumpForce, float walkForce, float hopForce, float glideForce, float stompForce)
        {
            JumpForce = jumpForce;
            WalkForce = walkForce;
            HopForce = hopForce;
            GlideForce = glideForce;
            StompForce = stompForce;
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            Contraption.Draw(spriteBatch);
        }
    }

    public class Doll
    {
        static int dollCount = 0;

        public int Combo = 0;

        public int Score = 0;

        bool IsBopReady = true;

        public bool IsGrounded = false;

        ContentManager Content;
        PhysicsSimulator physicsSimulator;
        
        Timer timer;
        Random random = new Random();

        Vector2 Tangent = new Vector2();
        Vector2 Normal = new Vector2();

        public DollState CurrentDollState;
        
        Stack<DollState> StackDollStates = new Stack<DollState>();
        Stack<DollState> StackNextDollStates = new Stack<DollState>();

        DollState stateStatue;
        DollState stateSpace;
        DollState stateBear;
        DollState stateCloth;
        DollState stateFleshy;
        DollState stateSquishy;
        DollState stateBoney;

        public Keys keyJump = Keys.Up;
        public Keys keyHopLeft = Keys.Left;
        public Keys keyHopRight = Keys.Right;
        public Keys keyStomp = Keys.Down;
        public Keys keyShead = Keys.RightShift;

        public Body Body
        {
            get { return CurrentDollState.Body; }
        }

        public Geom GeomHead
        {
            get { return CurrentDollState.GeomHead; }
        }

        public Geom GeomButt
        {
            get { return CurrentDollState.GeomButt; }
        }

        private List<Sprite> listHullSprites = new List<Sprite>();
        private List<Sprite> listDecalSprites = new List<Sprite>();

        private Vector2 FontDecalForce = new Vector2(0f, -50000f);

        private List<Contraption> listVodkas = new List<Contraption>();

        public Doll()
        {
            stateStatue = new DollState(80000, 1000, 1000, 1000, 10000);
            stateSpace = new DollState(100000, 3000, 5000, 5000, 5000);
            stateBear = new DollState(90000, 4000, 3000, 5000, 6000);
            stateCloth = new DollState(80000, 5000, 3000, 2000, 1000);
            stateFleshy = new DollState(80000, 7000, 3000, 2000, 1000);
            stateSquishy = new DollState(80000, 8000, 3000, 4000, 1000);
            stateBoney = new DollState(80000, 10000, 3000, 5000, 3000);

            StackDollStates.Push(stateBoney);
            StackDollStates.Push(stateSquishy);
            StackDollStates.Push(stateFleshy);
            StackDollStates.Push(stateCloth);
            StackDollStates.Push(stateBear);
            StackDollStates.Push(stateSpace);
            StackDollStates.Push(stateStatue);

            timer = new Timer();
            timer.Interval = 500;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);

            
        }

        public void LoadContent(string dollColor, string dollFace, PhysicsSimulator physicsSimulator, ContentManager content)
        {
            this.Content = content;
            this.physicsSimulator = physicsSimulator;

            dollCount++;

            //TCC: Load the contraption for each doll (body, geom, sprite)
            stateBoney.Contraption.Build("Doll_Bone.xml", physicsSimulator, content);
            stateSquishy.Contraption.Build("Doll_Squishy.xml", physicsSimulator, content);
            stateFleshy.Contraption.Build("Doll_Fleshy.xml", physicsSimulator, content);
            stateCloth.Contraption.Build("Doll_Cloth.xml", physicsSimulator, content);
            stateBear.Contraption.Build("Doll_Bear.xml", physicsSimulator, content);
            stateSpace.Contraption.Build("Doll_Space.xml", physicsSimulator, content);
            stateStatue.Contraption.Build("Doll_Statue.xml", physicsSimulator, content);

            //TCC: Configure the customized textures:
            string boneyHeadName = string.Format("Dolls/Bone_Head_{0}", dollFace);
            Texture2D textBoneHead = content.Load<Texture2D>(boneyHeadName);
            stateBoney.Contraption.mapSprites["t_head"].Texture = textBoneHead;

            string squishyHeadName = string.Format("Dolls/Squishy_Head_{0}", dollFace);
            Texture2D textSquishyHead = content.Load<Texture2D>(squishyHeadName);
            stateSquishy.Contraption.mapSprites["t_head"].Texture = textSquishyHead;

            string fleshyHeadName = string.Format("Dolls/Fleshy_Head_{0}", dollFace);
            Texture2D textFleshyHead = content.Load<Texture2D>(fleshyHeadName);
            stateFleshy.Contraption.mapSprites["t_head"].Texture = textFleshyHead;

            string clothHeadName = string.Format("Dolls/Cloth_Head_{0}_{1}", dollColor, dollFace);
            Texture2D textClothHead = content.Load<Texture2D>(clothHeadName);
            stateCloth.Contraption.mapSprites["t_head"].Texture = textClothHead;

            string clothButtName = string.Format("Dolls/Cloth_Butt_{0}", dollColor);
            Texture2D textClothButt = content.Load<Texture2D>(clothButtName);
            stateCloth.Contraption.mapSprites["t_butt"].Texture = textClothButt;

            CurrentDollState = StackDollStates.Peek();

            foreach (DollState dollState in StackDollStates)
            {
                dollState.Body = dollState.Contraption.mapBody["b_body"];
                dollState.GeomHead = dollState.Contraption.mapGeom["g_head"];
                dollState.GeomButt = dollState.Contraption.mapGeom["g_butt"];
                dollState.GeomHead.Tag = new GeomInfo(GeomType.DollHead, this);
                dollState.GeomButt.Tag = new GeomInfo(GeomType.DollButt, this);
                dollState.GeomButt.OnCollision += OnCollision;
                dollState.GeomButt.OnSeparation += OnSeperation;
                dollState.GeomHead.CollisionEnabled = false;
                dollState.GeomButt.CollisionEnabled = false;
                dollState.GeomHead.CollisionGroup = dollCount + 2;
                dollState.GeomButt.CollisionGroup = dollCount + 2;
                
                JointFactory.Instance.CreateFixedAngleLimitJoint(physicsSimulator, dollState.Body, 0, 0);
            }

            //StateDown();
            //StateDown();
            //StateDown();

            GeomButt.CollisionEnabled = true;
            GeomHead.CollisionEnabled = true;
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            IsBopReady = true;
            timer.Stop();
        }

        public void DisableStates(Stack<DollState> states)
        {
            foreach (DollState state in states)
            {
                state.GeomButt.OnCollision += OnCollisionInactive;
                state.GeomButt.OnSeparation += OnSeperationInactive;
                state.GeomHead.CollisionEnabled = false;
                state.GeomButt.CollisionEnabled = false;
            }
        }

        public void DisableAllStates()
        {
            DisableStates(StackDollStates);
            DisableStates(StackNextDollStates);
        }

        public void EnableState(DollState state)
        {
            state.GeomButt.OnCollision += OnCollision;
            state.GeomButt.OnSeparation += OnSeperation;
            state.GeomHead.CollisionEnabled = true;
            state.GeomButt.CollisionEnabled = true;
        }

        public void FollowCurrentState(DollState state)
        {
            state.Body.Position =  CurrentDollState.Body.Position;
            state.Body.ClearForce();
            state.Body.LinearVelocity = Vector2.Zero;
        }

        public void FollowCurrentState(Stack<DollState> states)
        {
            foreach (DollState state in states)
            {
                if (state != CurrentDollState)
                {
                    FollowCurrentState(state);
                }
            }
        }

        public void CreateHulls(DollState state)
        {
            Random rand = new Random();

            Body bodyButt = BodyFactory.Instance.CreateCircleBody(physicsSimulator, 1f, 1f);
            bodyButt.Position = state.GeomButt.Position;
            bodyButt.ApplyForce(CreateRandomHullForce());
            bodyButt.ApplyTorque(CreateRandomDecalTorque());
            Sprite spriteButt = new Sprite(state.Contraption.mapSprites["t_butt"].Texture, bodyButt);
            listHullSprites.Add(spriteButt);

            Body bodyHead = BodyFactory.Instance.CreateCircleBody(physicsSimulator, 1f, 1f);
            bodyHead.Position = state.GeomHead.Position;
            bodyHead.ApplyForce(CreateRandomHullForce());
            bodyHead.ApplyTorque(CreateRandomDecalTorque());
            Sprite spriteHead = new Sprite(state.Contraption.mapSprites["t_head"].Texture, bodyHead);
            listHullSprites.Add(spriteHead);
        }

        Vector2 CreateRandomHullForce()
        {
            int MaxXForce = 100000;
            int MaxYForce = 100000;

            float x = (float)random.Next(-MaxXForce, MaxXForce);
            float y = (float)random.Next(MaxXForce)*-1;

            return new Vector2(x, y);
        }

        Vector2 CreateRandomDecalForce()
        {
            int MaxXForce = 100000;
            int MaxYForce = 100000;

            float x = (float)random.Next(-MaxXForce, MaxXForce);
            float y = (float)random.Next(-MaxXForce, MaxXForce);

            return new Vector2(x, y);
        }

        float CreateRandomDecalTorque()
        {
            int MaxTorque = 500;
            return (float)random.Next(MaxTorque);
        }

        void CreateRandomForceDecal(Texture2D texture, Vector2 pos)
        {
            Body body = BodyFactory.Instance.CreateCircleBody(physicsSimulator, 1f, 1f);
            //Geom geom = GeomFactory.Instance.CreateCircleGeom(physicsSimulator, body, 1f, 3);
            //geom.Tag = new GeomInfo(GeomType.Decal, null);
            //geom.CollidesWith = CollisionCategory.Cat1;

            body.Position = pos;
            body.ApplyForce(CreateRandomDecalForce());
            body.ApplyTorque(CreateRandomDecalTorque());
            //body.IgnoreGravity = true;
            Sprite sprite = new Sprite(texture, body);
            listDecalSprites.Add(sprite);
        }

        void CreateHammerDecal(Vector2 pos)
        {
            CreateRandomForceDecal(TextureManager.Texture_Hammer, pos);
        }

        void CreateStarDecal(Vector2 pos)
        {
            CreateRandomForceDecal(TextureManager.Texture_Star, pos);
        }

        void CreateSickleDecal(Vector2 pos)
        {
            CreateRandomForceDecal(TextureManager.Texture_Sickle, pos);
        }

        void CreatePowerupDecal(Vector2 pos)
        {
            Texture2D texture = null;
            switch (Combo)
            {
                case 1:
                    texture = TextureManager.Texture_PowerupDecal1;
                    break;
                case 2:
                    texture = TextureManager.Texture_PowerupDecal2;
                    break;
                case 3:
                    texture = TextureManager.Texture_PowerupDecal3;
                    break;
                case 4:
                    texture = TextureManager.Texture_PowerupDecal4;
                    break;
                case 5:
                    texture = TextureManager.Texture_PowerupDecal5;
                    break;
                case 6:
                    texture = TextureManager.Texture_PowerupDecal6;
                    break;
                case 7:
                    texture = TextureManager.Texture_PowerupDecal7;
                    break;
                default:
                    texture = TextureManager.Texture_PowerupDecal7;
                    break;
            }
            if (texture != null)
            {
                Sprite sprite = new Sprite(texture, pos);
                listDecalSprites.Add(sprite);
            }
        }

        void CreateBangDecal(Vector2 pos, Vector2 normal)
        {
            Sprite sprite = new Sprite(TextureManager.Texture_ImpactLarge, pos);
            if (normal.Y < 0)
                normal.Y *= -1;
            else
                normal.X *= -1;
            sprite.Rotation = (float)Math.Atan2(normal.X, normal.Y);
            sprite.Scale = 0f;
            listDecalSprites.Add(sprite);
        }

        void CreateScoreFontDecal(Vector2 pos, int score)
        {
            FontDecal decal = new FontDecal(physicsSimulator, FontManager.Font_Hud, score.ToString(), pos, FontDecalForce);
            decal.dScale = -.1f;
            decal.dColor.A = 1;
            DecalManager.Add(decal);
        }

        public void StateUp()
        {
            if (StackNextDollStates.Count > 0)
            {
                DisableAllStates();

                DollState dollState = StackNextDollStates.Pop();
                StackDollStates.Push(dollState);
                CurrentDollState = StackDollStates.Peek();
                CurrentDollState.Body.Position = dollState.Body.Position;
                CurrentDollState.Body.ClearForce();
                CurrentDollState.Body.LinearVelocity = Vector2.Zero;

                EnableState(CurrentDollState);
            }
        }

        private void VodkaStorm()
        {
            for (int i = 0; i<1; i++)
            {
                //Contraption vodka = new Contraption();
                //vodka.Build("Vodka.xml", physicsSimulator, Content);

            }
        }

        public bool StateDown()
        {
            bool restart = true;

            DollState dollState = null;

            if (StackDollStates.Count > 1)
            {
                dollState = StackDollStates.Pop();
                CreateHulls(dollState);

                DisableAllStates();

                StackNextDollStates.Push(dollState);


                CurrentDollState = StackDollStates.Peek();
                CurrentDollState.Body.Position = dollState.Body.Position;
                CurrentDollState.Body.ClearForce();
                CurrentDollState.Body.LinearVelocity = Vector2.Zero;

                EnableState(CurrentDollState);

                restart = false;
                
            }

            return restart;
        }



        public void Jump()
        {
            if (IsGrounded)
            {
                Body.ApplyForce(Normal * CurrentDollState.JumpForce * -1);
            }
        }

        public void HopLeft()
        {
            if (IsGrounded)
            {
                Body.ApplyForce(Tangent * CurrentDollState.WalkForce * -1);
            }
            else
            {
                Body.ApplyForce(Tangent * CurrentDollState.GlideForce * -1);
            }
        }

        public void HopRight()
        {
            if (IsGrounded)
            {
                Body.ApplyForce(Tangent * CurrentDollState.WalkForce);
            }
            else
            {
                Body.ApplyForce(Tangent * CurrentDollState.GlideForce);
            }
        }

        public void Stomp()
        {
            if (!IsGrounded)
            {
                Body.ApplyForce(Normal * CurrentDollState.StompForce);
            }
        }

        public void Bop()
        {
            Combo++;
            Score += (Combo * 1000);
            CurrentDollState.Body.LinearVelocity.Y = 0;
            CurrentDollState.Body.ApplyForce(new Vector2(0, -100000));
            IsBopReady = false;
            timer.Start();
            //soundBop.Play(1f, 0f, 0f);
            if (Combo % 1 == 0)
            {
                StateUp();
            }

            if (Combo == 7)
            {
                VodkaStorm();
            }
        }

        public void Respawn()
        {
            while (CurrentDollState != stateCloth)
            {
                StateUp();
            }
            
            CurrentDollState.Body.Position = Vector2.Zero;
        }

        public void HandleInput(InputHelper input)
        {
            if (input.CurrentKeyboardState.IsKeyDown(keyHopLeft))HopLeft();
            if (input.CurrentKeyboardState.IsKeyDown(keyHopRight)) HopRight();
            if (input.CurrentKeyboardState.IsKeyDown(keyJump)) Jump();
            if (input.CurrentKeyboardState.IsKeyDown(keyStomp)) Stomp();
            if (input.CurrentKeyboardState.IsKeyDown(keyShead) && input.LastKeyboardState.IsKeyUp(keyShead))
            {
                if (!StateDown())
                {
                    Body.ApplyForce(new Vector2(0, -100000));
                }
            }
        }

        public void Update()
        {

            FollowCurrentState(StackDollStates);
            FollowCurrentState(StackNextDollStates);

            Tangent.X = (float)Math.Cos(Body.Rotation);
            Tangent.Y = (float)Math.Sin(Body.Rotation);
            Normal.X = -Tangent.Y;
            Normal.Y = Tangent.X;

            foreach (Sprite sprite in listHullSprites)
            {
                sprite.Scale += .05f;
                sprite.Alpha -= 1;
                Color color = new Color(255, 255, 255, sprite.Alpha);
                sprite.Color = color;
            }

            foreach (Sprite sprite in listDecalSprites)
            {
                sprite.Scale += .05f;
                sprite.Alpha-=1;
                Color color = new Color(255,255,255,sprite.Alpha);
                sprite.Color = color;
            }
            listHullSprites.RemoveAll(sprite => sprite.Alpha < 6);

            //List<Sprite> sprites = listDecalSprites.FindAll(sprite => sprite.Alpha < 6);
            //foreach (Sprite sprite in sprites)
            //{
            //        physicsSimulator.Remove(sprite.Body);
            //}
            listDecalSprites.RemoveAll(sprite => sprite.Alpha < 6);
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            if (CurrentDollState == stateBear)
            {
                stateCloth.Draw(spriteBatch);
            }

            if (CurrentDollState == stateSpace)
            {
                stateCloth.Draw(spriteBatch);
                stateBear.Draw(spriteBatch);
            }

            CurrentDollState.Draw(spriteBatch);

            foreach (Sprite sprite in listHullSprites)
            {
                sprite.Draw(spriteBatch);
            }

            foreach (Sprite sprite in listDecalSprites)
            {
                sprite.Draw(spriteBatch);
            }

            foreach (Contraption contraption in listVodkas)
            {
                contraption.Draw(spriteBatch);
            }
        }

        private void OnSeperationInactive(Geom geom1, Geom geom2)
        {

        }

        private void OnSeperation(Geom geom1, Geom geom2)
        {

            if (geom2.Tag != null && geom2.Tag is GeomInfo)
            {
                GeomInfo geomInfo = geom2.Tag as GeomInfo;

                switch (geomInfo.GeomType)
                {
                    case GeomType.DollButt:
                        break;
                    case GeomType.DollHead:
                        break;
                    case GeomType.Ground:
                        IsGrounded = false;
                        break;
                    default:
                        break;
                }
            }

        }

        private bool OnCollisionInactive(Geom geom1, Geom geom2, ContactList contactList)
        {
            return false;
        }

        private bool OnCollision(Geom geom1, Geom geom2, ContactList contactList)
        {
            bool result = true;
            
            if (geom2.Tag != null && geom2.Tag is GeomInfo)
            {
                GeomInfo geomInfo = geom2.Tag as GeomInfo;

                switch (geomInfo.GeomType)
                {
                    case GeomType.DollButt:
                        break;
                    case GeomType.DollHead:
                        if (geomInfo.Data is Doll)
                        {
                            Doll doll = geomInfo.Data as Doll;
                            if (doll != this)
                            {
                                if (IsBopReady)
                                {
                                    Bop();
                                    for (int i = 0; i < 5; i++)
                                    {
                                        CreateHammerDecal(contactList[0].Position);
                                        CreateSickleDecal(contactList[0].Position);
                                    }
                                    for (int i = 0; i < 25; i++)
                                    {
                                        CreateStarDecal(contactList[0].Position);
                                    }
                                    CreateScoreFontDecal(contactList[0].Position, (Combo * 10000));
                                    CreatePowerupDecal(contactList[0].Position);
                                    CreateBangDecal(contactList[0].Position, contactList[0].Normal);
                                    if (doll.StateDown())
                                    {
                                        doll.Respawn();
                                        Score += 1000;
                                        CreateScoreFontDecal(contactList[0].Position, 1000);
                                    }
                                }
                            }
                        }
                        break;
                    case GeomType.Ground:
                        Combo = 0;
                        IsGrounded = true;
                        break;
                    case GeomType.Decal:
                        result = false;
                        break;
                    default:
                        break;
                }
            }


            return result;
        }
    }
}
